/**密码校验高级规则*/
export const validateUserPwdRule = {
  checkPassword: function (user, pwd) {
    /**【形似度】-设定值*/
    const instanceLevel = 0.6;
    /**【包含相似度】-设定值*/
    const similarLevel = 0.6;
    /**当前【形似度】*/
    let instance = 0;
    /**当前【包含相似度】*/
    let similarRate = 0;
    if (user) {
      console.log('进行账户相关性校验****', user);
      /**用户名、密码-转换字符小写，方便形似检测*/
      const user_lower = thansferSimilarChar(user);
      const pwd_lower = thansferSimilarChar(pwd);
      /**当前【形似度】*/
      instance = LevenshteinDistance.init(pwd_lower, user_lower).get();
      /**当前【包含相似度】*/
      similarRate = this._similarityString(user_lower, pwd_lower);
    }
    const tip_similarRate = '【包含相似度】为' + similarRate;
    const tip_instance = '【形似度】为' + instance;

    // 返回值
    let result = {
      code: 0,
      msg: '',
      tip:
        '✅ 通过校验。 ' +
        tip_instance +
        tip_similarRate +
        '>>>参考值【形似度】为' +
        instanceLevel +
        '参考值【包含相似度】为' +
        similarLevel,
    };
    const pattern =
      /^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z~@#$*%^:;_.+-/]+$)(?![a-z0-9]+$)(?![a-z~@#$*%^:;_.+-/]+$)(?![0-9~@#$*%^:;_.+-/]+$)[a-zA-Z0-9~@#$*%^:;_.+-/]{8,}$/;
    // 密码长度
    if (pwd.length < 8) {
      result = {
        code: 11,
        msg: '密码长度小于8位',
        tip: '原因：【密码长度】不合规则。' + tip_instance,
      };
    }
    // 应该至少包括数字，小写字母，大写字母，特殊符号4类中的至少3类，
    else if (!pattern.test(pwd)) {
      result = {
        code: 12,
        msg: '密码需包含大写字母、小写字母、数字和特殊字符中的三类',
        tip: '原因：【密码字符】不包含3类规则。' + tip_instance,
      };
    }
    // 不得包含和用户名完整字符串
    else if (user && similarRate > similarLevel) {
      result = {
        code: 14,
        msg: '密码和账号相似，请调整',
        tip:
          '原因：【包含相似度】超过设定值。不得包含和用户名完整字符串。' +
          tip_similarRate +
          tip_instance,
      };
    }
    // 和用户名没有相关性，不得包含和用户名完整字符串，大小写变位，形似变换的字符串
    else if (user && instance > instanceLevel) {
      result = {
        code: 13,
        msg: '密码和账号相似，请调整',
        tip: '原因：【形似度】超过设定值。' + tip_instance,
      };
    }
    // 避免键盘排序密码
    else if (this._keyboardSortPassword(pwd)) {
      result = {
        code: 15,
        msg: '密码存在键盘排序字符，请调整',
        tip: '原因：【键盘排序】校验未通过。' + tip_instance,
      };
    }
    return result;
  },
  _similarityString: (u, p) => {
    // 按特定规则转换后不得包含和用户名完整字符串
    const a = p.indexOf(u);
    console.log('【包含度】检测进行中***', u, p);
    if (a > -1) {
      // 查找到账号完整字符串，说明密码包含账号
      return 1;
    }
    // 形似变换(编辑距离算法，编辑距离越小，字符串相似程度越大)
    return similarCompare(u, p);
  },

  /* 如果存在键盘排序字符串的话，就返回true，否则返回false。全部转小写，避免Wer这种 */
  _keyboardSortPassword: str => {
    const c1 = [
      ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+'],
      ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '{', '}', '|'],
      ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ':', '"'],
      ['z', 'x', 'c', 'v', 'b', 'n', 'm', '<', '>', '?'],
    ];
    const c2 = [
      ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='],
      ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'],
      ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'"],
      ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/'],
    ];
    str = str.toLowerCase().split('');
    //获取坐标位置
    const y = [];
    const x = [];
    for (let c = 0; c < str.length; c++) {
      y[c] = 0; //当做~`键处理
      x[c] = -1;
      for (let i = 0; i < c1.length; i++) {
        for (let j = 0; j < c1[i].length; j++) {
          if (str[c] === c1[i][j]) {
            y[c] = i;
            x[c] = j;
          }
        }
      }
      if (x[c] !== -1) continue;
      for (let i = 0; i < c2.length; i++) {
        for (let j = 0; j < c2[i].length; j++) {
          if (str[c] === c2[i][j]) {
            y[c] = i;
            x[c] = j;
          }
        }
      }
    }
    //匹配坐标连线
    for (let c = 1; c < str.length - 1; c++) {
      if (y[c - 1] === y[c] && y[c] === y[c + 1]) {
        if (
          (x[c - 1] + 1 === x[c] && x[c] + 1 === x[c + 1]) ||
          (x[c + 1] + 1 === x[c] && x[c] + 1 === x[c - 1])
        ) {
          return true;
        }
      } else if (x[c - 1] === x[c] && x[c] === x[c + 1]) {
        if (
          (y[c - 1] + 1 === y[c] && y[c] + 1 === y[c + 1]) ||
          (y[c + 1] + 1 === y[c] && y[c] + 1 === y[c - 1])
        ) {
          return true;
        }
      }
    }
    return false;
  },
};

/**相似度校验-编辑距离 */
const LevenshteinDistance = {
  _str1: null,
  _str3: null,
  _matrix: null,
  _isString: function (s) {
    return Object.prototype.toString.call(s) === '[object String]';
  },
  _isNumber: function (s) {
    return Object.prototype.toString.call(s) === '[object Number]';
  },
  init: function (str1, str2) {
    if (!this._isString(str1) || !this._isString(str2)) return;

    this._str1 = str1;
    this._str2 = str2;

    str1.length && str2.length && this._createMatrix(str1.length + 1, str2.length + 1);
    this._matrix && this._initMatrix();

    return this;
  },
  // 返回形似度
  get: function () {
    const res = 1 - this._getDistance() / Math.max(this._str1.length, this._str2.length);
    return res.toFixed(3);
  },
  // 计算编辑距离
  _getDistance: function () {
    const len1 = this._str1.length,
      len2 = this._str2.length;

    if (!len1 || !len2) return Math.max(len1, len2);

    const str1 = this._str1.split(''),
      str2 = this._str2.split('');

    let i = 0,
      j = 0,
      temp = 0;
    while (i++ < len1) {
      j = 0;
      while (j++ < len2) {
        temp = str1[i - 1] === str2[j - 1] ? 0 : 1;
        this._matrix[i][j] = Math.min(
          this._matrix[i - 1][j] + 1,
          this._matrix[i][j - 1] + 1,
          this._matrix[i - 1][j - 1] + temp
        );
      }
    }
    return this._matrix[i - 1][j - 1];
  },
  /*
   * 初始化矩阵
   * 为第一行、第一列赋值
   */
  _initMatrix: function () {
    const cols = this._matrix[0].length,
      rows = this._matrix.length;
    let l = Math.max(cols, rows);
    while (l--) {
      cols - 1 >= l && (this._matrix[0][l] = l);
      rows - 1 >= l && (this._matrix[l][0] = l);
    }
  },
  /*
   * 创建矩阵
   * n:行
   * m:列
   */
  _createMatrix: function (n, m) {
    if (!this._isNumber(n) || !this._isNumber(m) || n < 1 || m < 1) return;
    this._matrix = new Array(n);
    let i = 0;
    while (i < n) this._matrix[i++] = new Array(m);
  },
};

// 设A和B是两个字符串，使用最少的字符操作将字符串A转换为B。字符操作包括：（1）删除一个字符；（2）插入一个字符；（3）将一个字符改写为另一个字符。将字符串Ａ变换为字符串Ｂ所需要的最少字符操作数称为字符串Ａ到字符串Ｂ的编辑距离（Edit Distance）

/**
 * 相似度对比
 * @param s 文本1
 * @param t 文本2
 * @param f 小数位精确度，默认3位
 * @returns {string|number|*} 最大1. 比如 ：0.902
 */
const similarCompare = (s, t, f = 3) => {
  if (!s || !t) {
    return 0;
  }
  if (s === t) {
    return 1;
  }
  const n = s.length;
  const m = t.length;
  const l = n > m ? n : m;
  const d = [];

  const min = function (a, b, c) {
    return a < b ? (a < c ? a : c) : b < c ? b : c;
  };
  let i, j, si, tj, cost;
  if (n === 0) return m;
  if (m === 0) return n;
  for (i = 0; i <= n; i++) {
    d[i] = [];
    d[i][0] = i;
  }
  for (j = 0; j <= m; j++) {
    d[0][j] = j;
  }
  for (i = 1; i <= n; i++) {
    si = s.charAt(i - 1);
    for (j = 1; j <= m; j++) {
      tj = t.charAt(j - 1);
      if (si === tj) {
        cost = 0;
      } else {
        cost = 1;
      }
      d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
    }
  }
  const res = 1 - d[n][m] / l;
  return res.toFixed(f);
};

/**
 * 相似度转换：转小写字母
 * 形似字符转换
  "@"  ->  "a"
  "$"  ->  "s"
  "0"  ->  "o"
  "2"  ->  "z"
  "Ll|iI!"  ->  "1"
 */
const thansferSimilarChar = str => {
  if (!str) {
    return '';
  }
  const rt = str
    .replace(/@/g, 'a')
    .replace(/\$/g, 's')
    .replace(/0/g, 'o')
    .replace(/2/g, 'z')
    .replace(/([Ll|iI!])/gi, '1');
  return rt.toLowerCase();
};
